/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.model.quantize;

import java.util.ArrayList;
import org.jfonia.constants.ModelConstants;
import org.jfonia.constants.SymbolConstants;
import org.jfonia.model.PerformanceNode;
import org.jfonia.model.PerformanceSequence;
import org.jfonia.model.Sequence;
import org.jfonia.model.Staff;
import org.jfonia.model.TempoMap;
import org.jfonia.model.TempoNode;
import org.jfonia.model.ToneSequence;
import org.jfonia.model.elements.MusicalElement;
import org.jfonia.model.elements.TimeSignature;
import org.jfonia.staffnotation.BasicTimeSignature;
import percussiongenerator.model.Pair;

/**
 *
 * @author Jannes Plyson
 */

//works only on steady BPM and TimeSignature;
public class BPMQuantizer implements IQuantizer{
    private int smallestValue;
    private long gridOffset;
    private Grid grid;
    private long length;
    BasicTimeSignature basicTimeSignature;

    public BPMQuantizer(int smallestValue){
        this.smallestValue = smallestValue;
        gridOffset = 0;
        basicTimeSignature = new BasicTimeSignature(4,4);
    }

    public void setSmallestValue(int ticks){
        this.smallestValue = ticks;
    }

    private ArrayList<Long> getTimes(PerformanceSequence sequence){
        ArrayList<Long> onsetTimes = new ArrayList<Long>();
        for(int i = 0; i < sequence.size(); i++){
            PerformanceNode node = (PerformanceNode)sequence.getIntervalFromIndex(i);
            onsetTimes.add(node.getBegin());
        }
        onsetTimes.add(sequence.getDifference());//stop point for last note.
        return onsetTimes;
    }    

    public Grid createGrid(Staff staff){
        TempoMap tempoMap = staff.getTempoMap();
        Sequence<TimeSignature> sequence = staff.getSequence(TimeSignature.class);
        if(tempoMap != null && tempoMap.size() > 0 && sequence != null && sequence.size() > 0){
            TempoNode tempoNode = (TempoNode)tempoMap.getIntervalFromIndex(0);
            while(tempoMap.size() > 1){
                tempoMap.remove(tempoMap.size()-1);
            }
            TimeSignature ts = (TimeSignature)sequence.getIntervalFromIndex(0);
            while(sequence.size() > 1){
                sequence.remove(sequence.size()-1);
            }
            long gridWidth = (60l * ModelConstants.PERFORMANCE_TO_SECOND * smallestValue)/((long)tempoNode.getBPM()*SymbolConstants.DIVISOR/ts.getDenominator());
            return new Grid(gridWidth);
        }

        return null;
    }

    public void updateOffsets(PerformanceSequence sequence,Staff staff){        
        ArrayList<Long> onsetTimes = getTimes(sequence);
        grid = createGrid(staff);
        if(grid != null){
            ArrayList<Pair<Long,Long>> quantizedTimes = grid.fitToGrid(onsetTimes,gridOffset);
            int newStaffLength = getTicks(quantizedTimes.get(quantizedTimes.size()-1).first);
            TimeSignature ts = new TimeSignature(newStaffLength);
            Sequence<TimeSignature> tsSequence = new Sequence<TimeSignature>(TimeSignature.class);
            tsSequence.add(ts);
            ToneSequence seq = staff.getToneSequence();
            if(seq.size() > 0){
                for(int i = 0; i < sequence.size(); i++){
                    PerformanceNode node = (PerformanceNode)sequence.getIntervalFromIndex(i);
                    node.getQuantizeOffsetNode().setValue(quantizedTimes.get(i).first-node.getBegin());
                    MusicalElement musicalElement = (MusicalElement)seq.getIntervalFromIndex(i);
                    musicalElement.getBeginNode().setValue(getTicks(quantizedTimes.get(i).first));
                }
                MusicalElement musicalElement = (MusicalElement)seq.getIntervalFromIndex(seq.size()-1);
                musicalElement.setEnd(getTicks(quantizedTimes.get(quantizedTimes.size()-1).first));
            }
        }        
    }

    public int getTicks(long time) {        
        if(grid != null){            
            return (int)(grid.getGridValue(time, gridOffset)*smallestValue);
        }else{            
            return -1;
        }
    }

    public int getSmallestValue() {
        return smallestValue;
    }
}
